/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package kpsmart.gui;

import java.io.FileNotFoundException;
import java.net.Socket;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import javax.swing.JOptionPane;
import javax.xml.bind.JAXBException;
import javax.xml.bind.UnmarshalException;

import kpsmart.model.events.CostType;
import kpsmart.model.events.Event;
import kpsmart.model.events.Events;
import kpsmart.model.events.PriceType;
import kpsmart.model.routes.ChainedRoute;
import kpsmart.model.routes.Routes;
import network.IconManager;
import network.ServerController;
import network.ServerEventListener;

/**
 * Model class manages the tasks on the database - the view doesn't need to know anything about where or what the database is
 * @author Oliver Behncke
 * @author John Quinlivan
 */
public class Model implements ServerEventListener {

	private List<Event> events = null;
	private String logfilepath;
	private ChangeListener cl;

	/* Allows transmission of updated values */
	private ServerController serverController;

	private void connectToServer() {

		/* The socket which is connected to the remote server */
		Socket server = null;

		/* Allows an indefinite loop that can only end in either a successful connection or the user
		 * exiting */
		while (server == null) {

			/* Query the user for a location of the serverConnection */
			String serverName = (String) JOptionPane.showInputDialog(null,
					"            Please insert the name or location of the server"
							+ "\n  A valid location may be a computer name or an IP address",
							"Please define a server...",
							JOptionPane.PLAIN_MESSAGE,
							IconManager.getServerIcon(),
							null,
							"localhost");

			/* The user has chosen to cancel this query - and thus closing the application */
			if (serverName == null) {
				JOptionPane.showMessageDialog(null, "      Application is exiting!");
				System.exit(0);
			}

			/* Attempt a connection with the remote server */
			try {
				server = new Socket(serverName, 20000);
			} catch (Exception e) {
				int option = JOptionPane.showConfirmDialog(null, "Failed to create connection with server at \"" + serverName + "\"!"
						+ "\nWould you like to continue without a specified server?", "Unsuccessful connection!",
						JOptionPane.YES_NO_CANCEL_OPTION, JOptionPane.ERROR_MESSAGE);
				/* Client answers "yes" */
				if (option == 0) {
					break;
				} /* Client answers "no" */ else if (option == 1) {
					server = null;
				} else {
					JOptionPane.showMessageDialog(null, "      Application is exiting!");
					System.exit(0);
				}
			}
		}

		/* Instantiate the controller */
		if (server != null) {
			serverController = new ServerController(server);
			serverController.setServerEventListener(this);
			serverController.start();
		}
	}

	/**
	 * Constructor for model
	 * @param logfilepath database path
	 * @param cl listener to changes in the model (used by view)
	 */
	Model(String logfilepath, ChangeListener cl) {
		connectToServer();
		this.logfilepath = logfilepath;
		this.cl = cl;
	}

	/**
	 * Save the event to DB
	 * @return
	 * @throws JAXBException
	 */
	void saveEvent(Event e) throws JAXBException, FileNotFoundException {
		//        Events.saveEvent(e, logfilepath);
		//        reload();
		//        cl.change();
		sendEvent(e);
	}

	void sendEvent(Event e) {
		if (serverController != null) {
			serverController.sendEvent(e);
		}
	}

	/**
	 * Get the events of the model
	 * @return
	 * @throws JAXBException
	 */
	List<Event> getEvents() throws JAXBException {
		if (events == null) {
			reload();
		}
		return events;
	}

	/**
	 * Get a event from the event List. If out of bound return null
	 * @param index
	 * @return
	 * @throws JAXBException
	 */
	Event getEvent(int index) throws JAXBException {
		if (events == null) {
			reload();
		}
		if (index < 0 || index >= events.size()) {
			return null;
		} else {
			return events.get(index);
		}
	}

	/**
	 * Reload the List of Events used in the model
	 * @throws JAXBException
	 */
	void reload() throws JAXBException {
		try{
			events = Events.getEvents(logfilepath);
		} catch (UnmarshalException e){
			System.out.println("Malformed database received! Requesting new one!");
			if(serverController != null)
				serverController.requestDatabase();
		}
	}

	/**
	 * Get all PriceTypes that haven't been updated
	 * @return costTypes as List<CostType>
	 * @throws JAXBException
	 */
	List<CostType> getActiveCostTypes() throws JAXBException {
		return Events.getActiveCostTypes(getEvents());
	}

	/**
	 * Get all PriceTypes that haven't been updated
	 * @return
	 * @throws JAXBException
	 */
	Set<PriceType> getPriceTypes() throws JAXBException {
		return Events.getPriceTypes(getEvents());
	}

	/**
	 * Get origins of active routes
	 * @return origins as Set of Strings
	 * @throws JAXBException
	 */
	Collection<String> getOrigins() throws JAXBException {
		Set<String> result = new HashSet<String>();
		for (CostType c : getActiveCostTypes()) {
			result.add(c.getFrom());
		}
		return result;
	}

	/**
	 * Get only the New Zealand origins of current routes
	 * @return nzOrigins a Set of Strings
	 * @throws JAXBException
	 */
	Collection<String> getNZOrigins() throws JAXBException {
		Set<String> result = new HashSet<String>();
		for (String origin : getOrigins()) {
			if (Routes.inNewzealand(origin)) {
				result.add(origin);
			}
		}
		return result;
	}

	/**
	 * Get list of routes for origin
	 * @param origin
	 * @return list of routes for origin
	 * @throws JAXBException
	 */
	public List<ChainedRoute> getRoutes(String origin) throws JAXBException {
		return Routes.getRoutes(getEvents(), origin);
	}

	/**
	 * Get routes for certain origin and destination
	 * @param origin
	 * @param destination
	 * @return List<ChainedRoute>
	 * @throws JAXBException
	 */
	public List<ChainedRoute> getRoutes(String origin, String destination) throws JAXBException {
		return Routes.getRoutes(getEvents(), origin, destination);
	}

	/**
	 * Get a route for a origin, destination, priority, weight and volume
	 * 
	 * This implementation just delegates this task to the Routes utility class
	 * @param origin
	 * @param destination
	 * @param priority
	 * @param weight
	 * @param volume
	 * @return route - otherwise null
	 * @throws JAXBException
	 */
	public ChainedRoute getRoute(String origin, String destination, String priority, int weight, int volume) throws JAXBException {
		return Routes.getRoute(getEvents(), origin, destination, priority, weight, volume);
	}

	/**
	 * Get a price for a given route, priority, weight and volume
	 *
	 * This implementation just delegates this task to the Routes utility class
	 * @param route
	 * @param priority
	 * @param weight
	 * @param volume
	 * @return price as int value - -1 indicates failure
	 * @throws JAXBException
	 */
	public int getPriceForRoute(ChainedRoute route, String priority, int weight, int volume) throws JAXBException {
		return Routes.getPriceForRoute(events, route, priority, weight, volume);
	}

	@Override
	public void changeOnUpdatedDatabase() {
		cl.change();
	}

	@Override
	public String getLogFilePath() {
		return logfilepath;
	}

    void reconnect() {
        connectToServer();
    }
}
